今天的主題是如何提高julia程式碼的效能。
在前面的文章裡面一再提到,julia之所以可以非常快,是因為它在語法上讓使用者可以把它寫得很像C語言,讓編譯器容易產生高效的底層實作。
這篇文章會介紹幾個官方文件裡頭提到的Tips來提高程式碼的效能。
第一個重要的技巧是儘可能的宣告變數的type,像是這樣:
function f(a)
x=a[2]::Int32
return x+1
end
假設你已經確定知道a[2]會是一個Int32的整數,把x寫成a[2]::Int32不但可以告訴編譯器這份資訊,讓它容易把程式碼最佳化,同時也有assert的功能。一旦a[2]的值不屬於Int32,就會出現執行錯誤。我們還可以更進一步把function argument也加上型別標識:
function f(a::Array{Int32,1})
x=a[2]::Int32
return x+1
end
事實上,假如你把julia當做是C語言在使用的話,這麼做其實是一個很自然的選擇。在C裡面這個函數就會長得類似這樣:
int f(int* a)
{
int x=a[2];
return x+1;
}
另一個會影響效能的動作是在程式執行期間更改變數的型別,像這樣
x=2
x=2/3
這麼一來x的型別就從整數轉為浮點數。這是julia允許的,因為它還是有高階語言的特性,但是這會降低程式的效率。這也是可以預期的,因為在寫C的時候,變數的type也是一旦決定,就不能更改的。
第二個是要注意多維矩陣的元素取用順序。Julia是沿是矩陣的「行」來二維陣列的,亦即,下面這個二維矩陣:
[1 2;
3 4;]
在記憶體的存放順序是[1, 3 ,2 4],這個Fortran是一樣的,但是在C語言底下,記憶體存放順序就是[1,2,3,4]。所以要寫迴圈的時候,把column index當外圈會得到比較好的效能,也就是說
for colIndex=1:n
for rowIndex=1:n
A[rowIndex][colIndex]=0
end
會比
for rowIndex=1:n
for colIndex=1:n
A[rowIndex][colIndex]=0
end
快很多。
第三個就是儘量避免建立新的向量或是矩陣,例如: 可以用 x+y+z的話就不要用 sum([x,y,z])。因為除了建立矩陣這件事之外,另外還有垃圾回收(garbage collection)機制所帶來的時間成本。
要再度提醒大家的是,julia是一個動態性的腳本語言,不需要為了追求效率的極致把自己弄得綁手綁腳。開發初期當然還是寫得順最要緊,然後再從一些效率的瓶頸部份慢慢最佳化。如果每個地方都講求高效,那就去寫C/C++還比較直接。